/****************************************************** 
 *  Copyright 2018 IBM Corporation 
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at 
 *  http://www.apache.org/licenses/LICENSE-2.0 
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 *  See the License for the specific language governing permissions and 
 *  limitations under the License.
 */

package com.zhaoziqian.utils;

import com.zhaoziqian.client.FabricClient;
import com.zhaoziqian.config.EnvParam;
import com.zhaoziqian.model.NodeModel;
import com.zhaoziqian.model.OrdererModel;
import com.zhaoziqian.model.PeerModel;
import com.zhaoziqian.user.CAEnrollment;
import com.zhaoziqian.user.UserContext;
import org.apache.commons.lang3.StringUtils;
import org.hyperledger.fabric.sdk.EventHub;
import org.hyperledger.fabric.sdk.Orderer;
import org.hyperledger.fabric.sdk.Peer;
import org.hyperledger.fabric.sdk.exception.CryptoException;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.helper.Utils;

import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.zhaoziqian.config.Config.GRPCS;

/**
 * Created with fabric-cli
 * 作者： zhaoziqian
 * 时间： 2018/5/28 下午8:35
 * 功能：
 */

public class Util {

    public static final String EVENTHUB_SUFFIX = " - EVENT";

    /**
     * Serialize user
     *
     * @param userContext
     * @throws Exception
     */
    public static void writeUserContext(UserContext userContext) throws Exception {
        String directoryPath = "users/" + userContext.getAffiliation();
        String filePath = directoryPath + "/" + userContext.getName() + ".ser";
        File directory = new File(directoryPath);
        if (!directory.exists())
            directory.mkdirs();

        FileOutputStream file = new FileOutputStream(filePath);
        ObjectOutputStream out = new ObjectOutputStream(file);

        // Method for serialization of object
        out.writeObject(userContext);

        out.close();
        file.close();
    }

    /**
     * Deserialize user
     *
     * @param affiliation
     * @param username
     * @return
     * @throws Exception
     */
    public static UserContext readUserContext(String affiliation, String username) throws Exception {
        String filePath = "users/" + affiliation + "/" + username + ".ser";
        File file = new File(filePath);
        if (file.exists()) {
            // Reading the object from a file
            FileInputStream fileStream = new FileInputStream(filePath);
            ObjectInputStream in = new ObjectInputStream(fileStream);

            // Method for deserialization of object
            UserContext uContext = (UserContext) in.readObject();

            in.close();
            fileStream.close();
            return uContext;
        }

        return null;
    }

    /**
     * Create enrollment from key and certificate files.
     *
     * @param keyFolderPath
     * @param keyFileName
     * @param certFileName
     * @return
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws CryptoException
     */
    public static CAEnrollment getEnrollment(String keyFolderPath, String keyFileName, String certFolderPath, String certFileName)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, CryptoException {
        PrivateKey key = null;
        String certificate = null;
        InputStream isKey = null;
        BufferedReader brKey = null;

        try {

            isKey = new FileInputStream(keyFolderPath + File.separator + keyFileName);
            brKey = new BufferedReader(new InputStreamReader(isKey));
            StringBuilder keyBuilder = new StringBuilder();

            for (String line = brKey.readLine(); line != null; line = brKey.readLine()) {
                if (line.indexOf("PRIVATE") == -1) {
                    keyBuilder.append(line);
                }
            }

            certificate = new String(Files.readAllBytes(Paths.get(certFolderPath, certFileName)));

            byte[] encoded = DatatypeConverter.parseBase64Binary(keyBuilder.toString());
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
            KeyFactory kf = KeyFactory.getInstance("ECDSA");
            key = kf.generatePrivate(keySpec);
        } finally {
            isKey.close();
            brKey.close();
        }

        CAEnrollment enrollment = new CAEnrollment(key, certificate);
        return enrollment;
    }

    public static void cleanUp() {
        String directoryPath = "users";
        File directory = new File(directoryPath);
        deleteDirectory(directory);
    }

    public static boolean deleteDirectory(File dir) {
        if (dir.isDirectory()) {
            File[] children = dir.listFiles();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDirectory(children[i]);
                if (!success) {
                    return false;
                }
            }
        }

        // either file or an empty directory
        Logger.getLogger(Util.class.getName()).log(Level.INFO, "Deleting - " + dir.getName());
        return dir.delete();
    }

    /**
     * create grpcs properties
     *
     * @param model
     * @return
     */
    public static Properties createGrpcsProperties(NodeModel model) {
        Properties properties = new Properties();

        // create grpcs properties
        properties.setProperty("ordererWaitTimeMilliSecs", "GRPCS_WAIT_TIME");
        System.setProperty("GRPCS_WAIT_TIME", EnvParam.GRPCS_WAIT_TIME);
        properties.setProperty("pemFile", model.getTlsCacerts());
        properties.setProperty("hostnameOverride", model.getServerHostname());
        properties.setProperty("trustServerCertificate", "true");
        properties.setProperty("sslProvider", "openSSL"); // openSSL or JDK
        properties.setProperty("negotiationType", "TLS");   // TLS or plainText????
        return properties;

    }

    /**
     * create orderer , can auto set grpc prop
     *
     * @param fabClient
     * @param ordererModel
     * @return
     * @throws InvalidArgumentException
     * @throws MalformedURLException
     */
    public static Orderer createOrderer(FabricClient fabClient, OrdererModel ordererModel) throws InvalidArgumentException, MalformedURLException {
        Orderer orderer = null;
        Properties prop = Utils.parseGrpcUrl(ordererModel.getUrl());
        if (StringUtils.equals(GRPCS, prop.getProperty("protocol"))) {
            Properties ordererProp = Util.createGrpcsProperties(ordererModel);
            orderer = fabClient.getInstance().newOrderer(ordererModel.getServerHostname(), ordererModel.getUrl(), ordererProp);
        } else {
            orderer = fabClient.getInstance().newOrderer(ordererModel.getServerHostname(), ordererModel.getUrl());
        }
        return orderer;
    }

    /**
     * create peer , can auto set grpc prop
     *
     * @param fabClient
     * @param peerModel
     * @return
     * @throws InvalidArgumentException
     * @throws MalformedURLException
     */
    public static Peer createPeer(FabricClient fabClient, PeerModel peerModel) throws InvalidArgumentException, MalformedURLException {
        Peer peer = null;
        Properties prop = Utils.parseGrpcUrl(peerModel.getRequests());
        if (StringUtils.equals(GRPCS, prop.getProperty("protocol"))) {
            Properties peerProp = Util.createGrpcsProperties(peerModel);
            peer = fabClient.getInstance().newPeer(peerModel.getServerHostname(), peerModel.getRequests(), peerProp);
        } else {
            peer = fabClient.getInstance().newPeer(peerModel.getServerHostname(), peerModel.getRequests());
        }
        return peer;
    }

    /**
     * create eventHub ,can auto set grpc prop
     * @param fabClient
     * @param peerModel
     * @return
     * @throws InvalidArgumentException
     */
    public static EventHub createEventHub(FabricClient fabClient, PeerModel peerModel) throws InvalidArgumentException {
        EventHub eventHub = null;
        Properties grpcs = Utils.parseGrpcUrl(peerModel.getEvents());
        if (StringUtils.equals(GRPCS, grpcs.getProperty("protocol"))) {
            Properties peerProp = createGrpcsProperties(peerModel);
            eventHub = fabClient.getInstance().newEventHub(peerModel.getServerHostname() + EVENTHUB_SUFFIX, peerModel.getEvents(), peerProp);
        } else {
            eventHub = fabClient.getInstance().newEventHub(peerModel.getServerHostname() + EVENTHUB_SUFFIX, peerModel.getEvents());
        }
        return eventHub;
    }
}
